﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using TestByWire.BuildStrategies;
using TestByWire.Factories;
using TestByWire.Test.Fakes;

namespace TestByWire.Test.BuildStrategies
{
	using System.Reflection;

	[TestClass]
    public class AutoReturnBuildStrategyTest
    {
        private IUnityContainer _container;
        private Type _typeUnderTest;
        private AutoReturnBuildStrategyInTest _autoReturnBuildStrategy;
        private IStubInstanceFactory _stubInstanceFactory;

        [TestInitialize]
        public void PerTestSetup()
        {
            _container = new UnityContainer();
            _typeUnderTest = typeof(Computer);
            _stubInstanceFactory = MockRepository.GenerateDynamicMockWithRemoting<IStubInstanceFactory>();
            _autoReturnBuildStrategy = new AutoReturnBuildStrategyInTest(_container, _stubInstanceFactory, _typeUnderTest);
            new AutoInjectionBehavior(_typeUnderTest);
        }

        [TestMethod]
        public void Constructor_MustNotAcceptNullAsContainer()
        {
            var exception = AssertEx.Throws<ArgumentNullException>(() => new AutoReturnBuildStrategy(null, _stubInstanceFactory, _typeUnderTest));

            Assert.AreEqual("container", exception.ParamName);
        }

        [TestMethod]
        public void Constructor_MustNotAcceptNullAsInstanceFactory()
        {
            var exception = AssertEx.Throws<ArgumentNullException>(() => new AutoReturnBuildStrategy(_container, null, _typeUnderTest));

            Assert.AreEqual("stubInstanceFactory", exception.ParamName);
        }

        [TestMethod]
        public void Constructor_MustNotAcceptNullAsTypeUnderTest()
        {
            var exception = AssertEx.Throws<ArgumentNullException>(() => new AutoReturnBuildStrategy(_container, _stubInstanceFactory, null));

            Assert.AreEqual("typeUnderTest", exception.ParamName);
        }

        [TestMethod]
        public void PreBuildUp_MustNotAcceptNullAsContext()
        {
            var exception = AssertEx.Throws<ArgumentNullException>(() => _autoReturnBuildStrategy.PreBuildUp(null));

            Assert.AreEqual("context", exception.ParamName);
        }

		[TestMethod]
		public void PreBuildUp_MustCallCreatePropertySetAction_IfMethodUnderTestIsPropertyWithPublicSetter()
		{
			var builderContext = CreateBuilderContext(typeof(IMemory));
			var returnStub = MockRepository.GenerateStub<IResult>();
			_stubInstanceFactory.Stub(i => i.CreateStubInstanceOf(Arg<Type>.Is.Equal(typeof(IResult)), Arg<string>.Is.Anything)).Return(new StubCreationResult(returnStub, false));

			_autoReturnBuildStrategy.PreBuildUp(builderContext);

			_stubInstanceFactory.AssertWasCalled(s => s.CreatePropertySetAction(Arg<object>.Is.NotNull, Arg<MethodInfo>.Is.NotNull, Arg<object>.Is.NotNull));
		}

		[TestMethod]
		public void PreBuildUp_MustRegisterScenarioCall_IfMethodUnderTestIsPropertyWithPublicSetter()
		{
			var builderContext = CreateBuilderContext(typeof(IMemory));
			var returnStub = MockRepository.GenerateStub<IResult>();
			_stubInstanceFactory.Stub(i => i.CreateStubInstanceOf(Arg<Type>.Is.Equal(typeof(IResult)), Arg<string>.Is.Anything)).Return(new StubCreationResult(returnStub, false));

			_autoReturnBuildStrategy.PreBuildUp(builderContext);

			Assert.IsTrue(AutoReturnBuildStrategyInTest.SetupStubReturnValueWasCalled);
		}

		[TestMethod]
        public void PreBuildUp_MustNotStubMethodWithReturnType()
        {
            // A
			var builderContext = CreateBuilderContext(typeof(ClassWithSingleCompositeReturn));
			var returnStub = MockRepository.GenerateStub<ComplexTestClassReturn>();
            _stubInstanceFactory.Stub(i => i.CreateStubInstanceOf(Arg<Type>.Is.Equal(typeof(ComplexTestClassReturn)), Arg<string>.Is.Anything)).Return(new StubCreationResult(returnStub, false));

            // A
            _autoReturnBuildStrategy.PreBuildUp(builderContext);

            // A
            Assert.IsFalse(AutoReturnBuildStrategyInTest.SetupStubReturnValueWasCalled);
        }

		private IBuilderContext CreateBuilderContext(Type buildKeyType)
		{
			var builderContext = MockRepository.GenerateStub<IBuilderContext>();
			var buildKey = new NamedTypeBuildKey(buildKeyType);
			builderContext.Stub(b => b.OriginalBuildKey).Return(buildKey);
			builderContext.BuildKey = buildKey;
			_container.RegisterInstance(buildKeyType, MockRepository.GenerateStub(buildKeyType));

			return builderContext;
		}
        
        internal class AutoReturnBuildStrategyInTest : AutoReturnBuildStrategy
        {
            [ThreadStatic]
            internal static bool SetupStubReturnValueWasCalled;
            
            [ThreadStatic]
            internal static object StubbedReturnInstance;

            public AutoReturnBuildStrategyInTest(IUnityContainer container, IStubInstanceFactory stubInstanceFactory, Type typeUnderTest)
                : base(container, stubInstanceFactory, typeUnderTest)
            {
                SetupStubReturnValueWasCalled = false;
                StubbedReturnInstance = null;
            }

            protected internal override void SetupStubReturnValue(object instanceBeingStubbed, Expression<Action<object>> action, object stubbedReturnInstance)
            {
                SetupStubReturnValueWasCalled = true;
                StubbedReturnInstance = stubbedReturnInstance;
            }
        }
    }
}
